using System;
using System.Collections.Generic;
using System.Text;
using System.Xml;
using System.ComponentModel;
using System.Globalization;
using C4F.DevKit.WebServices.Properties;
using C4F.DevKit.WebServices.Datasets;

namespace C4F.DevKit.WebServices
{
    /// <summary>
    /// Exposes GetPost,AddPost and DeletePost functionality on del.icio.us
    /// </summary>
    [ToolboxItem(true)]
    public class Delicious : C4FWebServicesBase
    {
        #region Private members
        /// <summary>
        /// Specifies the user-name, if credentials are needed to access url
        /// </summary>
        private string urlUserName = string.Empty;
        /// <summary>
        /// Password for urlUserName, in case credentials are needed to access the url
        /// </summary>
        private string urlPassword = string.Empty;
        /// <summary>
        /// Base Url
        /// </summary>
        private static string baseUrl = Resources.DeliciousBaseUrl;

        #endregion

        #region Accessors
        /// <summary>
        /// Specify the user-name, if credentials are needed to access url
        /// </summary>
        [Category("Delicious")]
        [Description("Specify the user-name, if credentials are needed to access url.")]
        public string UrlUserName
        {
            get { return this.urlUserName; }
            set { this.urlUserName = value == null ? string.Empty : value.Trim(); }
        }

        /// <summary>
        /// Password for urlUserName, in case credentials are needed to access the url.
        /// This field is ignored if UserName is empty.
        /// </summary>
        [Category("Delicious")]
        [Description("Password for urlUserName, in case credentials are needed to access the url. This field is ignored if UserName is empty.")]
        public string UrlPassword
        {
            get { return this.urlPassword; }
            set { this.urlPassword = value == null ? string.Empty : value.Trim(); }
        }
        #endregion

        #region Public functions

        /// <summary>
        /// Gets the bookmarks posted on Delicious by the user
        /// </summary>
        /// <returns>Returns all the posts of the user</returns>
        /// <exception cref="InvalidOperationException">The UrlUserName is not set (OR) the UrlPassword is not set</exception>
        public List<DeliciousPost> GetPost()
        {
            if (String.IsNullOrEmpty(this.UrlUserName) || String.IsNullOrEmpty(this.UrlPassword))
                throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, Resources.ErrorSpecifyBothArguments, "UrlUserName", "UrlPassword"));

            string requestUrl = Delicious.GetRequestUrlForGetPost(null, null, null);

            // GetPost () responses
            string responseData = base.GetQueryResponse(requestUrl, this.UrlUserName, this.UrlPassword);

            // Parse the data and return results
            return Delicious.ParseGetResponseXml(responseData);
        }

        /// <summary>
        /// Gets the bookmarks posted on Delicious by the user
        /// </summary>
        /// <param name="tag">Filter by this Category (optional)</param>
        /// <param name="date">Filter by this Date (optional)</param>
        /// <param name="url">Filter by this Url (optional)</param>
        /// <returns>Returns the list of posts matching the arguments</returns>
        /// <exception cref="InvalidOperationException">The UrlUserName is not set (OR) the UrlPassword is not set</exception>
        public List<DeliciousPost> GetPost(string tag, string date, string url)
        {
            if (String.IsNullOrEmpty(this.UrlUserName) || String.IsNullOrEmpty(this.UrlPassword))
                throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, Resources.ErrorSpecifyBothArguments, "UrlUserName", "UrlPassword"));

            string requestUrl = Delicious.GetRequestUrlForGetPost(tag, date, url);

            // GetPost () responses
            string responseData = base.GetQueryResponse(requestUrl, this.UrlUserName,this.UrlPassword);

            // Parse the data and return results
            return Delicious.ParseGetResponseXml(responseData);
        }

        /// <summary>
        /// Add a post to del.icio.us
        /// </summary>
        /// <param name="url">The url of the item</param>
        /// <param name="description">The description of the item</param>
        /// <param name="extended">Notes for the item (optional)</param>
        /// <param name="tags">Tags for the item (optional)</param>
        /// <param name="dateStamp">Datestamp of the item (optional)</param>
        /// <param name="replace">if 'no' dont replace post if given url has already been posted (optional)</param>
        /// <param name="shared">if 'no' make the item private (optional)</param>
        /// <returns>True if Add successful</returns>
        /// <exception cref="InvalidOperationException">The UrlUserName is not set (OR) the UrlPassword is not set</exception>
        /// <exception cref="ArgumentException">The url is not specified (OR) the description is not specified</exception>
        public bool AddPost(string url, string description, string extended,
            string tags, string dateStamp, string replace, string shared)
        {
            if (String.IsNullOrEmpty(this.UrlUserName) || String.IsNullOrEmpty(this.UrlPassword))
                throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, Resources.ErrorSpecifyBothArguments, "UrlUserName", "UrlPassword"));

            if (String.IsNullOrEmpty(url))
                throw new ArgumentException(string.Format(CultureInfo.InvariantCulture, Resources.ErrorSpecifyArgument, "url"));

            if (String.IsNullOrEmpty(description))
                throw new ArgumentException(string.Format(CultureInfo.InvariantCulture, Resources.ErrorSpecifyArgument, "description"));

            string requestUrl = Delicious.GetRequestUrlForAddPost(url, description,
                extended, tags, dateStamp, replace, shared);

            // GetPost () responses
            string responseData = base.GetQueryResponse(requestUrl, this.UrlUserName, this.UrlPassword);

            // Parse the data and return results
            return Delicious.ParseAddDeleteResponseXml(responseData);

        }

        /// <summary>
        /// Add a post to del.icio.us
        /// </summary>
        /// <param name="url">The url of the item</param>
        /// <param name="description">The description of the item</param>
        /// <param name="extended">Notes for the item (optional)</param>
        /// <param name="tags">Tags for the item (optional)</param>
        /// <returns>True if Add successful</returns>
        /// <exception cref="InvalidOperationException">The UrlUserName is not set (OR) the UrlPassword is not set</exception>
        /// <exception cref="ArgumentException">The url is not specified (OR) the description is not specified</exception>
        public bool AddPost(string url, string description, string extended,
            string tags)
        {
            if (String.IsNullOrEmpty(this.UrlUserName) || String.IsNullOrEmpty(this.UrlPassword))
                throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, Resources.ErrorSpecifyBothArguments, "UrlUserName", "UrlPassword"));

            if (String.IsNullOrEmpty(url))
                throw new ArgumentException(string.Format(CultureInfo.InvariantCulture, Resources.ErrorSpecifyArgument, "url"));

            if (String.IsNullOrEmpty(description))
                throw new ArgumentException(string.Format(CultureInfo.InvariantCulture, Resources.ErrorSpecifyArgument, "description"));

            string requestUrl = Delicious.GetRequestUrlForAddPost(url, description,
                extended, tags, null, null, null);

            // GetPost () responses
            string responseData = base.GetQueryResponse(requestUrl, this.UrlUserName, this.UrlPassword);

            // Parse the data and return results
            return Delicious.ParseAddDeleteResponseXml(responseData);

        }

        /// <summary>
        /// Add a post to del.icio.us
        /// </summary>
        /// <param name="url">The url of the item</param>
        /// <param name="description">The description of the item</param>
        /// <returns>True if Add successful</returns>
        /// <exception cref="InvalidOperationException">The UrlUserName is not set (OR) the UrlPassword is not set</exception>
        /// <exception cref="ArgumentException">The url is not specified (OR) the description is not specified</exception>
        public bool AddPost(string url, string description)
        {
            if (String.IsNullOrEmpty(this.UrlUserName) || String.IsNullOrEmpty(this.UrlPassword))
                throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, Resources.ErrorSpecifyBothArguments, "UrlUserName", "UrlPassword"));

            if (String.IsNullOrEmpty(url))
                throw new ArgumentException(string.Format(CultureInfo.InvariantCulture, Resources.ErrorSpecifyArgument, "url")); 

            if (String.IsNullOrEmpty(description))
                throw new ArgumentException(string.Format(CultureInfo.InvariantCulture, Resources.ErrorSpecifyArgument, "description"));

            string requestUrl = Delicious.GetRequestUrlForAddPost(url, description, null, null, null, null, null);

            // GetPost () responses
            string responseData = base.GetQueryResponse(requestUrl, this.UrlUserName, this.UrlPassword);

            // Parse the data and return results
            return Delicious.ParseAddDeleteResponseXml(responseData);
        }
        /// <summary>
        /// Delete a post from del.icio.us
        /// </summary>
        /// <param name="url">The url of the item</param>
        /// <returns>successful if true</returns>
        /// <exception cref="InvalidOperationException">The UrlUserName is not set (OR) the UrlPassword is not set</exception>
        /// <exception cref="ArgumentNullException">The url is not specified</exception>
        public bool DeletePost(string url)
        {
            if (String.IsNullOrEmpty(this.UrlUserName) || String.IsNullOrEmpty(this.UrlPassword))
                throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, Resources.ErrorSpecifyBothArguments, "UrlUserName", "UrlPassword"));
         
            string requestUrl = Delicious.GetRequestUrlForDeletePost(url);

            // GetPost () responses
            string responseData = base.GetQueryResponse(requestUrl, this.UrlUserName, this.UrlPassword);

            // Parse the data and return results
            return Delicious.ParseAddDeleteResponseXml(responseData);
        }

        /// <summary>
        /// Gets the bookmarks posted on Delicious by the user and returns the result in a dataset
        /// </summary>
        /// <param name="tag">Filter by this Category (optional)</param>
        /// <param name="date">Filter by this Date (optional)</param>
        /// <param name="url">Filter by this Url (optional)</param>
        /// <returns>Dataset containing the list of posts matching the arguments</returns>
        /// <exception cref="InvalidOperationException">The UrlUserName is not set (OR) the UrlPassword is not set</exception>
        public DS_Delicious GetPost_DS(string tag, string date, string url)
        {
            List<DeliciousPost> posts = GetPost(tag, date, url);
            DS_Delicious dataset = new DS_Delicious();
            foreach (DeliciousPost post in posts)
                dataset.DeliciousPost.AddDeliciousPostRow(post.Url, post.Description, post.Extended, post.Hash, post.Others,
                    post.Tag, post.Time);
            return dataset;
        }

        /// <summary>
        /// Gets the bookmarks posted on Delicious by the user and returns the result in a dataset
        /// </summary>
        /// <returns>Dataset containing the list of all the posts of the user</returns>
        /// <exception cref="InvalidOperationException">The UrlUserName is not set (OR) the UrlPassword is not set</exception>
        public DS_Delicious GetPost_DS()
        {
            List<DeliciousPost> posts = GetPost();
            DS_Delicious dataset = new DS_Delicious();
            foreach (DeliciousPost post in posts)
                dataset.DeliciousPost.AddDeliciousPostRow(post.Url, post.Description, post.Extended, post.Hash, post.Others,
                    post.Tag, post.Time);
            return dataset;
        }
        #endregion

        #region Private functions
        /// <summary>
        /// Prepare Request Url to get post items
        /// </summary>
        /// <param name="tag">Category of the post item</param>
        /// <param name="dt">Date</param>
        /// <param name="url">Url of the item</param>
        /// <returns>Request Url</returns>
        private static string GetRequestUrlForGetPost(string tag, string dt, string url)
        {
            StringBuilder queryBuilder = new StringBuilder(Delicious.baseUrl + "get?");

            // Add tag
            if (!string.IsNullOrEmpty(tag) && tag.Trim().Length > 0)
            {
                queryBuilder.Append("&tag=");
                queryBuilder.Append(tag);
            }

            // Add dt
            if (!string.IsNullOrEmpty(dt) && dt.Trim().Length > 0)
            {
                queryBuilder.Append("&dt=");
                queryBuilder.Append(dt);
            }

            // Add url
            if (!string.IsNullOrEmpty(url) && url.Trim().Length > 0)
            {
                queryBuilder.Append("&url=");
                queryBuilder.Append(url);
            }
            return queryBuilder.ToString();
        }

        /// <summary>
        /// Parses Response data
        /// </summary>
        /// <param name="responseData">response Data</param>
        /// <returns>List of Post Items</returns>
        private static List<DeliciousPost> ParseGetResponseXml(string responseData)
        {
            XmlDocument xmlDocument = new XmlDocument();
            List<DeliciousPost> postDataList = null;

            xmlDocument.LoadXml(responseData);
            // Retrieve result nodes
            XmlNodeList postNodes =
                xmlDocument.DocumentElement.SelectNodes("/posts/post");

            if (postNodes == null || postNodes.Count == 0)
                return postDataList;

            postDataList = new List<DeliciousPost>(postNodes.Count);

            for (int i = 0; i < postNodes.Count; i++)
            {
                XmlNode postNode = postNodes[i];
                DeliciousPost postData = new DeliciousPost();
                postDataList.Add(postData);

                // Populate PostData object
                if (postNode == null || postNode.Attributes == null ||
                    postNode.Attributes.Count <= 0)
                    return postDataList;

                postData.Url = postNode.Attributes["href"] == null ? null :
                    postNode.Attributes["href"].Value;

                postData.Description = postNode.Attributes["description"] == null ?
                    null : postNode.Attributes["description"].Value;

                postData.Extended = postNode.Attributes["extended"] == null ? null :
                    postNode.Attributes["extended"].Value;

                postData.Hash = postNode.Attributes["hash"] == null ? null :
                    postNode.Attributes["hash"].Value;

                postData.Others = postNode.Attributes["others"] == null ? null :
                    postNode.Attributes["others"].Value;

                postData.Tag = postNode.Attributes["tag"] == null ? null :
                    postNode.Attributes["tag"].Value;

                postData.Time = postNode.Attributes["time"] == null ? null :
                    postNode.Attributes["time"].Value;
            }
            return postDataList;
        }

        /// <summary>
        /// Prepare Request Url to add post items
        /// </summary>
        /// <param name="url">The url of the item</param>
        /// <param name="description">The description of the item</param>
        /// <param name="extended">Notes for the item</param>
        /// <param name="tags">Tags for the item tional)</param>
        /// <param name="dateStamp">Datestamp of the item</param>
        /// <param name="replace">if 'no' dont replace post if given url has already been posted (optional)</param>
        /// <param name="shared">if 'no' make the item private (optional)</param>
        /// <returns>True if Add successful</returns>
        private static string GetRequestUrlForAddPost(string url, string description,
            string extended, string tags, string dateStamp, string replace,
            string shared)
        {
            StringBuilder queryBuilder = new StringBuilder(Delicious.baseUrl + "add?");

            // Add url
            if (!string.IsNullOrEmpty(url) && url.Trim().Length > 0)
                queryBuilder.Append("&url=" + url);

            // Add description
            if (!string.IsNullOrEmpty(description) && description.Trim().Length > 0)
                queryBuilder.Append("&description=" + description);

            // Add extended
            if (!string.IsNullOrEmpty(extended) && extended.Trim().Length > 0)
                queryBuilder.Append("&extended=" + extended);

            // Add tags
            if (!string.IsNullOrEmpty(tags) && tags.Trim().Length > 0)
                queryBuilder.Append("&tags=" + tags);

            // Add dateStamp
            if (!string.IsNullOrEmpty(dateStamp) && dateStamp.Trim().Length > 0)
                queryBuilder.Append("&dt=" + dateStamp);

            // Add replace
            if (!string.IsNullOrEmpty(replace) && replace.Trim().Length > 0)
                queryBuilder.Append("&replace=" + replace);

            // Add shared
            if (!string.IsNullOrEmpty(shared) && shared.Trim().Length > 0)
                queryBuilder.Append("&shared=" + shared);

            return queryBuilder.ToString();
        }

        /// <summary>
        /// Parses the response data to check whether add/delete successful 
        /// </summary>
        /// <param name="responseData">Response data</param>
        /// <returns>True is successful</returns>
        private static bool ParseAddDeleteResponseXml(string responseData)
        {
            XmlDocument xmlDocument = new XmlDocument();

            xmlDocument.LoadXml(responseData);

            // Retrieve result nodes
            XmlNode resultNode =
                xmlDocument.DocumentElement.SelectSingleNode("/result");

            if (resultNode == null || resultNode.Attributes == null ||
                    resultNode.Attributes["code"] == null)
                return false;

            if (string.Compare(resultNode.Attributes["code"].Value, "done", false, CultureInfo.InvariantCulture) == 0)
                return true;

            return false;
        }

        /// <summary>
        /// Prepare request url to delete post item
        /// </summary>
        /// <param name="url">Url of the item</param>
        /// <returns>Request Url</returns>
        /// <exception cref="ArgumentNullException">The url is not specified</exception>
        private static string GetRequestUrlForDeletePost(string url)
        {
            StringBuilder queryBuilder = new StringBuilder(Delicious.baseUrl + "delete?");

            // Add url
            if (!string.IsNullOrEmpty(url) && url.Trim().Length > 0)
                queryBuilder.Append("&url=" + url);
            else
                throw new ArgumentNullException("url");

            return queryBuilder.ToString();
        }
        #endregion
    }

    /// <summary>
    /// Represents a post on del.icio.us
    /// </summary>
    public class DeliciousPost
    {
        #region Private Members
        /// <summary>
        /// Book mark URL
        /// </summary>
        private string url;
        /// <summary>
        /// Description of the book mark
        /// </summary>
        private string description;
        /// <summary>
        /// Extended Note about the book mark
        /// </summary>
        private string extended;
        /// <summary>
        /// Hash value
        /// </summary>
        private string hash;
        /// <summary>
        /// No. of other people that have book marked the same URL
        /// </summary>
        private string others;
        /// <summary>
        /// Category
        /// </summary>
        private string tag;
        /// <summary>
        /// Time when posted
        /// </summary>
        private string time;
        #endregion

        #region Public Properties
        /// <summary>
        /// Book mark URL
        /// </summary>
        public string Url
        {
            get { return this.url; }
            set { this.url = value; }
        }
        /// <summary>
        /// Description of the book mark
        /// </summary>
        public string Description
        {
            get { return this.description; }
            set { this.description = value; }
        }
        /// <summary>
        /// Extended Note about the book mark
        /// </summary>
        public string Extended
        {
            get { return this.extended; }
            set { this.extended = value; }
        }
        /// <summary>
        /// Hash value
        /// </summary>
        public string Hash
        {
            get { return this.hash; }
            set { this.hash = value; }
        }
        /// <summary>
        /// No. of other people that have book marked the same URL
        /// </summary>
        public string Others
        {
            get { return this.others; }
            set { this.others = value; }
        }
        /// <summary>
        /// Category
        /// </summary>
        public string Tag
        {
            get { return this.tag; }
            set { this.tag = value; }
        }
        /// <summary>
        /// Time when posted
        /// </summary>
        public string Time
        {
            get { return this.time; }
            set { this.time = value; }
        }
        #endregion
    }
}